package com.general.framework.data.redis;

import com.general.framework.core.lang.Lists;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.BoundGeoOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * RedisGeo 地理位置
 * @author leejean
 * @date 2022年11月18 11:05:56
 */
@Component
public class RedisGeo<T> {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 添加成员
     * @param longitude 经度
     * @param latitude 纬度
     * @param member 成员
     */
    public Long geoAdd(String key, double longitude, double latitude, String member){
        return stringRedisTemplate.opsForGeo().add(key, new Point(longitude, latitude), member);
    }

    /**
     * 获取两个成员的距离
     * @param member1 成员1
     * @param member2 成员2
     */
    public Distance geoDistance(String key, String member1, String member2){
        return stringRedisTemplate.opsForGeo().distance(key, member1, member2);
    }

    /**
     * 获取两个成员的距离
     * @param member1 成员1
     * @param member2 成员2
     * @param metric 距离单位（枚举）（km、m）
     */
    public Distance geoDistance(String key, String member1, String member2, Metrics metric){
        return stringRedisTemplate.opsForGeo().distance(key, member1, member2, metric);
    }

    /**
     * 获取成员经纬度
     * @param members 成员
     */
    public List<Point> geoPosition(String key, String... members){
        return stringRedisTemplate.opsForGeo().position(key, members);
    }

    /**
     * 获取某个成员附近（距离范围内）的成员
     * @param member 成员
     * @param distance 距离
     * @param metric  距离单位（枚举）（km、m）
     */
    public List<Geo> geoNear(String key, String member, double distance, Metrics metric){
        GeoResults<RedisGeoCommands.GeoLocation<String>> geoResults = stringRedisTemplate.opsForGeo().radius(key, member, new Distance(distance, metric));
        if (Objects.isNull(geoResults)) {
            return Lists.emptyList();
        }
        return getGeoList(geoResults);
    }

    public List<Geo> geoNear(String key, double longitude, double latitude, int distance, Metrics metrics, int limit) {
        BoundGeoOperations boundGeoOperations = stringRedisTemplate.boundGeoOps(key);
        Point point = new Point(longitude, latitude);
        Circle within = new Circle(point, new Distance(distance, metrics));
        RedisGeoCommands.GeoRadiusCommandArgs geoRadiusArgs = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs();
        geoRadiusArgs = geoRadiusArgs.includeDistance();
        geoRadiusArgs.limit(limit);
        geoRadiusArgs.sortAscending();
        GeoResults<RedisGeoCommands.GeoLocation<String>> geoResults = boundGeoOperations.radius(within, geoRadiusArgs);
        if (Objects.isNull(geoResults)) {
            return Lists.emptyList();
        }
        return getGeoList(geoResults);
    }

    private List<Geo> getGeoList(GeoResults<RedisGeoCommands.GeoLocation<String>> geoResults) {
        List<Geo> result = new ArrayList<>();
        Geo geo;
        for (GeoResult<RedisGeoCommands.GeoLocation<String>> geoResult : geoResults.getContent()) {
            geo = new Geo();
            geo.setMember(geoResult.getContent().getName());
            geo.setDistance(geoResult.getDistance().getValue());
            result.add(geo);
        }
        return result;
    }

    /**
     * 获取某个成员附近（距离范围内）的成员
     * @param member 成员
     * @param distance 距离
     * @param metric  距离单位（枚举）（km、m）
     * @param args 示例：RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeCoordinates().includeDistance().limit(1).sortAscending();
     *             includeCoordinates：结果包含坐标，includeDistance：结果包含距离，limit：返回数量：sort...：排序
     * @return GeoResults
     * geoResult.getContent().getName() 元素名称
     * geoResult.getContent().getPoint() 元素坐标
     * geoResult.getDistance() 元素距离
     */
    public GeoResults<RedisGeoCommands.GeoLocation<String>> geoNear(String key, String member, double distance, Metrics metric, RedisGeoCommands.GeoRadiusCommandArgs args){
        return stringRedisTemplate.opsForGeo().radius(key, member, new Distance(distance, metric), args);
    }

}
